#include	"Object.h"
#include	"Config.h"
#include	<exception>
#include	<fstream>
#include	<sstream>
#include	<iostream>

Object::Object(const Config & config)
	:	m_config(&config)
	,	m_input("")
	,	m_matLib(config)
	,	m_name("")
	,	m_vertCoord()
	,	m_texCoord()
	,	m_group()
{
}

Object::~Object()
{
	Cleanup();
}

void Object::Load(const std::string & file)
{
	if(m_config->Verbose())
	{
		std::cout << "Processing OBJ: " << file << std::endl;
	}
	m_input = file;
	std::ifstream stream(m_input.c_str());
	if(stream)
	{
		std::string line;
		while(stream)
		{
			std::getline(stream, line);
			if((stream) && (line.length() != 0) && (line[0] != '#'))
			{
				ParseLine(line);
			}
		}
		stream.close();
		FixIndexes();
	}
	else
	{
		throw std::exception(std::string("Error: Can not open file \"" + file + "\".").c_str());
	}
}

void Object::Cleanup()
{
	m_matLib.Cleanup();
	m_name = "";
	m_vertCoord.clear();
	m_texCoord.clear();
	m_group.clear();
}

CObject Object::CompiledObject() const
{
	if(m_config->Verbose())
	{
		std::cout << "Converting read data..." << std::endl;
	}
	CObject co(const_cast< Config & >(*m_config), m_name);
	for(std::list< Material >::const_iterator it = m_matLib.Materials().begin();
		it != m_matLib.Materials().end(); ++it)
	{
		CMaterial mat;
		mat.shininess = (*it).shininess;
		mat.diffuse.r = (*it).diffuse.r;
		mat.diffuse.g = (*it).diffuse.g;
		mat.diffuse.b = (*it).diffuse.b;
		mat.diffuse.a = (*it).alpha;
		mat.ambient.r = (*it).ambient.r;
		mat.ambient.g = (*it).ambient.g;
		mat.ambient.b = (*it).ambient.b;
		mat.ambient.a = (*it).alpha;
		mat.specular.r = (*it).specular.r;
		mat.specular.g = (*it).specular.g;
		mat.specular.b = (*it).specular.b;
		mat.specular.a = (*it).alpha;
		mat.emission.r = (*it).emission.r;
		mat.emission.g = (*it).emission.g;
		mat.emission.b = (*it).emission.b;
		mat.emission.a = (*it).alpha;
		mat.texture = (*it).texture;
		co.AddMaterial(mat);
	}
	for(std::list< TriangleGroup >::const_iterator it = m_group.begin();
		it != m_group.end(); ++it)
	{
		unsigned int matIndex = m_matLib.MaterialIndex((*it).material);
		if(matIndex == unsigned int(-1))
		{
			throw std::exception("Error: There is a triangle group, without assigned material.");
		}
		for(std::list< Triangle >::const_iterator t = (*it).triangle.begin();
			t != (*it).triangle.end(); ++t)
		{
			CTriangle tri;
			tri.mat = matIndex;
			tri.p1.x = m_vertCoord.at((*t).p1.vertex).x;
			tri.p1.y = m_vertCoord.at((*t).p1.vertex).y;
			tri.p1.z = m_vertCoord.at((*t).p1.vertex).z;
			tri.p1.u = m_texCoord.at((*t).p1.texture).u;
			tri.p1.v = m_texCoord.at((*t).p1.texture).v;
			tri.p2.x = m_vertCoord.at((*t).p2.vertex).x;
			tri.p2.y = m_vertCoord.at((*t).p2.vertex).y;
			tri.p2.z = m_vertCoord.at((*t).p2.vertex).z;
			tri.p2.u = m_texCoord.at((*t).p2.texture).u;
			tri.p2.v = m_texCoord.at((*t).p2.texture).v;
			tri.p3.x = m_vertCoord.at((*t).p3.vertex).x;
			tri.p3.y = m_vertCoord.at((*t).p3.vertex).y;
			tri.p3.z = m_vertCoord.at((*t).p3.vertex).z;
			tri.p3.u = m_texCoord.at((*t).p3.texture).u;
			tri.p3.v = m_texCoord.at((*t).p3.texture).v;
			co.AddTriangle(tri);
		}
	}
	return co;
}

void Object::ParseLine(const std::string & line)
{
	std::istringstream stream(line);
	std::string firstWord;
	stream >> firstWord;
	if(firstWord == "mtllib")
	{
		std::string matName;
		stream >> matName;
		if(!stream)
		{
			throw std::exception("Error: mtllib command requires a parameter - name of the mtl file.");
		}
		std::string::size_type lastSlash = m_input.find_last_of('\\');
		if(lastSlash != std::string::npos)
		{
			matName = m_input.substr(0, lastSlash + 1) + matName;
		}
		m_matLib.Load(matName);
	}
	else if(firstWord == "o")
	{
		std::string name;
		stream >> name;
		if(!stream)
		{
			throw std::exception("Error: o command requires a parameter - name of the object.");
		}
		m_name = name;
	}
	else if(firstWord == "v")
	{
		VertexCoordinates vert;
		stream >> vert.x >> vert.y >> vert.z;
		if(!stream)
		{
			throw std::exception("Error: v command requires three parameters - coordinates of the vertex.");
		}
		m_vertCoord.push_back(vert);
	}
	else if(firstWord == "vt")
	{
		TextureCoordinates tex;
		stream >> tex.u >> tex.v;
		if(!stream)
		{
			throw std::exception("Error: vt command requires two parameters - coordinates of the texture map.");
		}
		m_texCoord.push_back(tex);
	}
	else if(firstWord == "g")
	{
		TriangleGroup tg;
		stream >> tg.name;
		if(!stream)
		{
			throw std::exception("Error: g command requires a parameter - name of the group.");
		}
		m_group.push_back(tg);
	}
	else if(firstWord == "usemtl")
	{
		std::string matName;
		stream >> matName;
		if(!stream)
		{
			throw std::exception("Error: usemtl command requires a parameter - name of the material.");
		}
		(*m_group.rbegin()).material = matName;
	}
	else if(firstWord == "f")
	{
		Triangle triangle;
		std::string pt1, pt2, pt3;
		stream >> pt1 >> pt2 >> pt3;
		if(!stream)
		{
			throw std::exception("Error: f command requires three parameters - indexes of the vertexes & textures.");
		}
		ParsePoint(triangle.p1, pt1);
		ParsePoint(triangle.p2, pt2);
		ParsePoint(triangle.p3, pt3);
		(*m_group.rbegin()).triangle.push_back(triangle);
	}
	else 
	{
		std::cout << "Warning: unsupported command in OBJ \"" << firstWord << "\"" << std::endl;
	}
}

void Object::ParsePoint(Point & pt, const std::string & text)
{
	std::string::size_type first = text.find_first_of('/');
	if(first == std::string::npos)
	{
		throw std::exception("Error: invalid point description, indexes must be separated with \'/\' character.");
	}
	std::string::size_type second = text.find_first_of('/', first + 1);
	Point val;
	std::istringstream stream(text.substr(0, first));
	stream >> val.vertex;
	if(!stream)
	{
		throw std::exception("Error: couldn\'t read vertex index.");
	}
	if((second - first) > 1)
	{
		stream.clear();
		if(second != std::string::npos)
		{
			stream.str(text.substr(first + 1, second - first - 1));
		}
		else
		{
			stream.str(text.substr(first + 1));
		}
		stream >> val.texture;
		if(!stream)
		{
			throw std::exception("Error: couldn\'t read texture index.");
		}
	}
	else
	{
		val.texture = 0;
	}
	pt = val;
}

void Object::FixIndexes()
{
	if(m_config->Verbose())
	{
		std::cout << "Fixing vertex and texture indexes..." << std::endl;
	}
	for(std::list< TriangleGroup >::iterator group = m_group.begin(); group != m_group.end(); ++group)
	{
		for(std::list< Triangle >::iterator triangle = (*group).triangle.begin();
			triangle != (*group).triangle.end(); ++triangle)
		{
			(*triangle).p1.vertex -= 1;
			(*triangle).p1.texture -= 1;
			(*triangle).p2.vertex -= 1;
			(*triangle).p2.texture -= 1;
			(*triangle).p3.vertex -= 1;
			(*triangle).p3.texture -= 1;
		}
	}
}

